const events = ['resize', 'scroll']

const batchStyle = (el, style = {}, className = {}) => {
	for (let k in style) {
		el.style[k] = style[k]
	}
	for (let k in className) {
		if (className[k] && !el.classList.contains(k)) {
			el.classList.add(k)
		} else if (!className[k] && el.classList.contains(k)) {
			el.classList.remove(k)
		}
	}
}

export default class Sticky {
	// 在mescroll使用设置containerCls = 'uni-scroll-view'
	constructor(el, vm, top = 88, containerCls = '') {
		if (!containerCls) {
			throw new Error('container-cls不能为空')
		}
		this.el = el
		this.vm = vm
		this.stock = null
		this.unsubscribers = []
		this.isPending = false
		this.containerCls = containerCls
		this.state = {
			isTopSticky: null,
			height: null,
			width: null,
			xOffset: null,
		}
		this.lastState = {
			top: null,
			sticked: false,
		}

		const offset = {
			top: Number(top)
		}
		const zIndex = '99999'
		const onStick = null

		this.options = {
			topOffset: Number(uni.upx2px(offset.top)) || 0,
			zIndex: zIndex,
			onStick: onStick,
		}
	}

	doBind() {
		if (this.unsubscribers.length > 0) {
			return
		}
		const {
			el,
			vm
		} = this
		vm.$nextTick(() => {
			this.placeholderEl = document.createElement('div')
			this.containerEl = this.getContainerEl()
			el.parentNode.insertBefore(this.placeholderEl, el)
			events.forEach(event => {
				const fn = this.update.bind(this)
				this.unsubscribers.push(() => window.removeEventListener(event, fn))
				this.unsubscribers.push(() => this.containerEl.removeEventListener(event, fn))
				window.addEventListener(event, fn, {
					passive: true
				})
				this.containerEl.addEventListener(event, fn, {
					passive: true
				})
			})
		})
	}

	doUnbind() {
		this.unsubscribers.forEach(fn => fn())
		this.unsubscribers = []
		this.resetElement()
	}

	update() {
		if (!this.isPending) {
			requestAnimationFrame(() => {
				this.isPending = false
				this.recomputeState()
				this.updateElements()
			})
			this.isPending = true
		}
	}

	isTopSticky() {
		const fromTop = this.state.placeholderElRect.top
		const topBreakpoint = this.options.topOffset
		return fromTop <= topBreakpoint
	}

	recomputeState() {
		this.state = Object.assign({}, this.state, {
			height: this.getHeight(),
			width: this.getWidth(),
			xOffset: this.getXOffset(),
			placeholderElRect: this.getPlaceholderElRect(),
			containerElRect: this.getContainerElRect(),
		})
		this.state.isTopSticky = this.isTopSticky()
	}

	updateElements() {
		const placeholderStyle = {
			paddingTop: 0
		}
		const elStyle = {
			position: 'static',
			top: 'auto',
			left: 'auto',
			zIndex: this.options.zIndex,
		}
		const placeholderClassName = {
			'vue-sticky-placeholder': true
		}
		const elClassName = {
			'vue-sticky-el': true,
			'top-sticky': false,
		}
		if (this.state.isTopSticky) {
			elStyle.position = 'fixed'
			elStyle.top = this.options.topOffset + 'px'
			elStyle.left = this.state.xOffset + 'px'
			elStyle.right = 0
			placeholderStyle.paddingTop = this.state.height + 'px'
			elClassName['top-sticky'] = true
		} else {
			placeholderStyle.paddingTop = 0
		}
		batchStyle(this.el, elStyle, elClassName)
		batchStyle(this.placeholderEl, placeholderStyle, placeholderClassName)
	}

	resetElement() {
		;
		['position', 'top', 'left', 'width', 'zIndex'].forEach(attr => {
			this.el.style.removeProperty(attr)
		})
		this.el.classList.remove('top-sticky')
		const {
			parentNode
		} = this.placeholderEl
		if (parentNode) {
			parentNode.removeChild(this.placeholderEl)
		}
	}

	getContainerEl() {
		let node = this.el.parentNode
		while (node && node.tagName !== 'HTML' && node.tagName !== 'BODY' && node.nodeType === 1) {
			if (node.classList.contains(this.containerCls)) {
				return node
			}
			node = node.parentNode
		}
		return this.el.parentNode
	}

	getXOffset() {
		return this.placeholderEl.getBoundingClientRect().left
	}

	getWidth() {
		return this.placeholderEl.getBoundingClientRect().width
	}

	getHeight() {
		return this.el.getBoundingClientRect().height
	}

	getPlaceholderElRect() {
		return this.placeholderEl.getBoundingClientRect()
	}

	getContainerElRect() {
		return this.containerEl.getBoundingClientRect()
	}
}